package org.taobao.ibatiside.core.filemodel;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.lang.ArrayUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.taobao.ibatiside.core.IbatisConstant;
import org.taobao.ibatiside.core.util.BatisEntityResolver;


public class FileProber {
	public static final char[] COMMENT_MARK= {'!','-','-'};
	/**
	 * Extract public and system id from a DTD xml file.
	 * @param dtdInstanceFile
	 * @param encoding
	 * @return [0] publicId [1] SystemId
	 */
	public static String[] getPublicAndSystemId(InputStream dtdInstanceFile, String encoding){
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(dtdInstanceFile,encoding));
			int one_char;
			int boundary = 0, prog=1, dtd=2, element=3, comment=4;
			int other=0,pub_s=1,pub_e=2, sys_s=3,sys_e=4;
			int status = boundary;
			int doc_part = other;
			StringBuffer buffer = new StringBuffer(200);
			String[] psid = new String[2];
			char pre_one = 0, pre_two = 0;
			while ((one_char = reader.read()) !=-1)
			{
				//care spaces in DTD declaration
				char be_char = (char)one_char;
				if (status != dtd && Character.isSpaceChar(be_char)){
					continue;
				}
				if (one_char == '<'){
					if (status == comment) continue;
					reader.mark(8);
					int fw_one = reader.read();
					if (fw_one == '?'){
						status = prog;
						reader.reset();
					}else if (fw_one == '!'){
						reader.reset();
						char[] b3 = new char[3];
						reader.mark(3);
						reader.read(b3);
						if (Arrays.equals(b3, COMMENT_MARK)){
							status = comment;
						}else{
							status = dtd; //simple check
						}
						reader.reset();
					}else{
						status = element;
						break; //No DTD but it has root elements.
					}
//					System.out.println("Start "+status);
				}else if (one_char == '"' ){
					//only care in DTD
					if (status == dtd){
						if (doc_part == other){
							doc_part = pub_s;
						}else if (doc_part == pub_s){
							doc_part = pub_e;
							psid[0] = buffer.toString();
							buffer.setLength(0);
						}else if (doc_part == pub_e){
							doc_part = sys_s;
						}else if (doc_part == sys_s){
							doc_part = sys_e;
							psid[1] = buffer.toString();
							buffer.setLength(0);
						}
					}
				}else if (one_char == '>'){
					if (status == dtd){
						break;
					}else{
//						System.out.println("End "+status);
						if (pre_one == '-' && pre_two == '-' && status == comment){
							continue;
						}else{
							status = boundary;
						}
					}
				}else{
					if (status == dtd && (doc_part == pub_s || doc_part == sys_s)){
						buffer.append(be_char);
					}
				}
				pre_one = be_char;
				pre_two = pre_one;
			}
			return psid;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		//FileReader reader = new FileReader()
		return ArrayUtils.EMPTY_STRING_ARRAY;
	}
	public static boolean isValidateDtdFile(InputStream stream, String encoding, String[] publicId, String[] systemId){
		String[] ids = getPublicAndSystemId(stream, encoding);
		if (publicId.length <1 || systemId.length < 1 || ids.length !=2 || ids[0] == null || ids[1] == null){
			return false;
		}
		boolean match = false;
		for (String pid : publicId){
			if(pid.equalsIgnoreCase(ids[0])){
				match=true;
				break;
			}
		}
		if (!match) return false;
		for (String sid : systemId){
			if (sid.equalsIgnoreCase(ids[1])){
				match = true;
				break;
			}
		}
		return match;
		
	}
	public static boolean isBatisXMLFile(IFile file){
		return isIbatis2Config(file) || isIbatis2Mapper(file) || isMyIbatisConfig(file) || isMyIbatisMapper(file);
	}
	public static boolean isIbatis2Config(IFile file){
		try {
			return isValidateDtdFile(file.getContents(), file.getCharset(), IbatisConstant.V2_CONFIG_PUBLIC_ID, IbatisConstant.V2_CONFIG_SYSTEM_ID);
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	public static boolean isIbatis2Mapper(IFile file){
		try {
			return isValidateDtdFile(file.getContents(), file.getCharset(), IbatisConstant.V2_MAP_PUBLIC_ID, IbatisConstant.V2_MAP_SYSTEM_ID);
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	public static boolean isMyIbatisConfig(IFile file){
		try {
			return isValidateDtdFile(file.getContents(), file.getCharset(), IbatisConstant.V3_CONFIG_PUBLIC_ID, IbatisConstant.V3_CONFIG_SYSTEM_ID);
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	public static boolean isMyIbatisMapper(IFile file){
		try {
			return isValidateDtdFile(file.getContents(), file.getCharset(), IbatisConstant.V3_MAP_PUBLIC_ID, IbatisConstant.V3_MAP_SYSTEM_ID);
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	public static List<String> getMapResources(IFile file, int version){
		XMLStreamReader reader  = null;
		try{
			InputStream in = file.getContents();
			XMLInputFactory xmlif = XMLInputFactory.newInstance();
			xmlif.setXMLResolver(new BatisEntityResolver());
			reader = xmlif.createXMLStreamReader(in);
			return version == IbatisConstant.VERSION_TWO ? getV2MapResources(reader) : getV3MapResources(reader);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if (reader != null)
				try {
					reader.close();
				} catch (XMLStreamException e) {
					e.printStackTrace();
				}
		}
		return Collections.<String>emptyList();
	}
	public static List<String> getV2MapResources(XMLStreamReader reader) throws XMLStreamException{
		List<String> resources = new ArrayList<String>();
		int level = 0;
		while (reader.hasNext()){
			switch (reader.next()){
			case XMLStreamConstants.START_ELEMENT:
				++level;
				if (level==2 && "sqlMap".equals(reader.getName().getLocalPart())){
					resources.add(reader.getAttributeValue(null, "resource"));
				}
				break;
			case XMLStreamConstants.END_ELEMENT:
				--level;
				break;
			}
			
		}
		return resources;
	}
	public static List<String> getV3MapResources(XMLStreamReader reader) throws XMLStreamException{
		List<String> resources = new ArrayList<String>();
		int level = 0;
		boolean in_mappers = false;
		while (reader.hasNext()){
			switch (reader.next()){
			case XMLStreamConstants.START_ELEMENT:
				++level;
				String name = reader.getName().getLocalPart();
				if (level==3 && in_mappers && "mapper".equals(name)){
					resources.add(reader.getAttributeValue(null, "resource"));
				}else if(level==2){
					in_mappers = "mappers".equals(name);
				}
				break;
			case XMLStreamConstants.END_ELEMENT:
				--level;
				break;
			}
			
		}
		return resources;
	}
	public static List<IbatisStatement> getMapStatements(IFile file){
		XMLStreamReader reader = null;
		try{
			InputStream in = file.getContents();
			XMLInputFactory xmlif = XMLInputFactory.newInstance();
			xmlif.setXMLResolver(new BatisEntityResolver());
			reader = xmlif.createXMLStreamReader(in);
			return getMapStatements(reader);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if (reader != null)
				try {
					reader.close();
				} catch (XMLStreamException e) {
					e.printStackTrace();
				}
		}
		return Collections.<IbatisStatement>emptyList();
	}
	public static List<IbatisStatement> getMapStatements(XMLStreamReader reader) throws XMLStreamException{
		List<IbatisStatement> stmts = new ArrayList<IbatisStatement>();
		int level = 0;
		IbatisStatement snapshot = null;
		int previous_offset = 0; // comment space or another element
		while (reader.hasNext()){
			switch (reader.next()){
			case XMLStreamConstants.START_ELEMENT:
				++level;
				String name = reader.getName().getLocalPart();
				if (level==2){
					//insert
					int category = "insert".equals(name) ? IbatisConstant.STMT_INSERT :  "update".equals(name) ? IbatisConstant.STMT_UPDATE : 
									"delete".equals(name) ? IbatisConstant.STMT_DELETE : "select".equals(name) ? IbatisConstant.STMT_SELECT :
										"procedure".equals(name) ? IbatisConstant.STMT_PROCEDURE : "statement".equals(name) ? IbatisConstant.STMT_GENERAL : -1;
					if (category != -1){
						snapshot = new IbatisStatement();
						snapshot.setCategory(category);
						snapshot.setId(reader.getAttributeValue(null, "id"));
						Location location = reader.getLocation();
						snapshot.setOffset(previous_offset-1);
						snapshot.setLineNumber(location.getLineNumber());
						snapshot.setColNumber(location.getColumnNumber());
					}else{
						snapshot = null;
					}
				}
				break;
			case XMLStreamConstants.END_ELEMENT:
				int char_offset = reader.getLocation().getCharacterOffset();
				if (level==2 && snapshot != null){
					snapshot.setLength(char_offset-snapshot.getOffset());
					snapshot.setEndOffset(char_offset);
					stmts.add(snapshot);
				}
				previous_offset = char_offset;
				--level;
				break;
			case XMLStreamConstants.COMMENT:
			case XMLStreamConstants.SPACE:
				if (level==1){
					previous_offset = reader.getLocation().getCharacterOffset();
				}
				break;
			}
			
		}
		return stmts;
	}
	
	public static int getIbatisVerion(IProject project) {
		try {
			if (project.isAccessible() && !project.isHidden()
					&& project.hasNature(JavaCore.NATURE_ID)) {
				IJavaProject jp = JavaCore.create(project);
				// first try ibatis2
				boolean validate = true;
				for (String type : IbatisConstant.V2_TYPE_TO_CHECK) {
					validate = validate && (jp.findType(type) != null);
				}
				if (validate) {
					return IbatisConstant.VERSION_TWO;
				} else {
					validate = true;
					for (String type : IbatisConstant.V3_TYPE_TO_CHECK) {
						validate = validate && (jp.findType(type) != null);
					}
					if (validate)
						return IbatisConstant.VERSION_MYBATIS;
				}
			}
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return IbatisConstant.NOT_IBATIS;
	}
	public static boolean isValidSpringConfigurationFile(IFile file){
		if (file == null || !file.isAccessible() || !"xml".equalsIgnoreCase(file.getFileExtension())){
			return false;
		}
		try {
			return isSpringConfigurationFile(file.getContents());
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}
	public static final String BEANS_LITERAL="beans";
	public static final String SPRING_NS="http://www.springframework.org/schema/beans";
	public static boolean isSpringConfigurationFile(InputStream file){
		XMLStreamReader reader = null;
		try{
			XMLInputFactory xmlif = XMLInputFactory.newInstance();
			reader = xmlif.createXMLStreamReader(file);
			int level = 0;
			while (reader.hasNext()){
				if (level >= 1) break;
				switch (reader.next()){
				case XMLStreamConstants.START_ELEMENT:
					++level;
					if (level==1){
						QName node_name = reader.getName();
						if (BEANS_LITERAL.equals(node_name.getLocalPart()) && SPRING_NS.equals(node_name.getNamespaceURI())){
							return true;
						}
					}
					break;
				case XMLStreamConstants.END_ELEMENT:
					--level;
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if (reader != null){
				try {
					reader.close();
				} catch (XMLStreamException e) {
					e.printStackTrace();
				}
			}
		}
		return false;
		
	}
}
